/* 
 Copyright Paul James Mutton, 2001-2004, http://www.jibble.org/

 This file is part of SimpleFTP.

 This software is dual-licensed, allowing you to choose between the GNU
 General Public License (GPL) and the www.jibble.org Commercial License.
 Since the GPL may be too restrictive for use in a proprietary application,
 a commercial license is also provided. Full license information can be
 found at http://www.jibble.org/licenses/

 $Author: pjm2 $
 $Id: SimpleFTP.java,v 1.2 2004/05/29 19:27:37 pjm2 Exp $

 */
package simpleftp.client.control;

import java.io.*;
import java.net.*;
import java.util.*;
import org.json.simple.JSONObject;

/**
 * SimpleFTP is a simple package that implements a Java FTP client. With
 * SimpleFTP, you can connect to an FTP server and upload multiple files.
 * <p>
 * Copyright Paul Mutton,
 * <a href="http://www.jibble.org/">http://www.jibble.org/</a>
 *
 */
public class SimpleFTP {

    /**
     * Create an instance of SimpleFTP.
     */
    public SimpleFTP() {

    }

    /**
     * Connects to the default port of an FTP server and logs in as
     * anonymous/anonymous.
     *
     * @param host
     * @throws java.io.IOException
     */
    public void connect(String host) throws IOException {
        connect(host, 21);
    }

    /**
     * Connects to an FTP server and logs in as anonymous/anonymous.
     *
     * @param host
     * @param port
     * @throws java.io.IOException
     */
    public void connect(String host, int port) throws IOException {
        connect(host, port, "anonymous", "anonymous");
    }

    /**
     * Connects to an FTP server and logs in with the supplied username and
     * password.
     *
     * @param host
     * @param port
     * @param user
     * @param pass
     * @throws java.io.IOException
     */
    public void connect(String host, int port, String user, String pass) throws IOException {
        if (socket != null) {
            throw new IOException("SimpleFTP is already connected. Disconnect first.");
        }

        socket = new Socket(host, port);
        reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        // TO BE COMPLETED - 1 LINE
        is = new ObjectInputStream(socket.getInputStream());

        String response = readLine();
        if (!response.startsWith("220 ")) {
            throw new IOException("SimpleFTP received an unknown response when connecting to the FTP server: " + response);
        }

        sendLine("USER " + user);

        response = readLine();

        if (!response.startsWith("331 ")) {
            System.out.println("2");
            throw new IOException("SimpleFTP received an unknown response after sending the user: " + response);
        }
        sendLine("PASS " + pass);

        response = readLine();
        if (!response.startsWith("230 ")) {
            throw new IOException("SimpleFTP was unable to log in with the supplied password: " + response);
        }
        // Now logged in.
    }

    /**
     * Disconnects from the FTP server.
     *
     * @throws java.io.IOException
     */
    public void disconnect() throws IOException {
        try {
            sendLine("QUIT");
        } finally {
            socket = null;
        }
    }

    /**
     * Returns the working directory of the FTP server it is connected to.
     *
     * @return
     * @throws java.io.IOException
     */
    public String pwd() throws IOException {
        sendLine("PWD");
        String dir = null;
        String response = readLine();
        if (response.startsWith("257 ")) {
            System.out.println("PWD " + response);
            int fsLastIndex = response.lastIndexOf(System.getProperty("file.separator"));
            if (fsLastIndex > 0) {
                dir = response.substring(fsLastIndex + 1);
            } else {
                dir = response.substring(4);
            }
        }
        return dir;
    }

    /**
     * Returns a list of files from a directory specified.
     *
     * @return
     * @throws java.io.IOException
     */
    public String ls() throws IOException {
        sendLine("LIST");
        String list = null;
        String response = readLine();
        if (response.startsWith("200 ")) {
            list = response.split(" ")[1];
        }

        // TO BE COMPLETED - READ THE SERIALIZED OBJECT FROM THE NETWORK - 5 LINES
        try {
            //fileTree = (File) ois.readObject(); //uncomment thsis to have the filetree
            oJson = (JSONObject) is.readObject();
        } catch (ClassNotFoundException e) {
        }

        return list;
    }

    /**
     * Changes the working directory (like cd). Returns true if successful.
     *
     * @param dir
     * @return
     * @throws java.io.IOException
     */
    public boolean cwd(String dir) throws IOException {
        sendLine("CWD " + dir);
        String response = readLine();
        return (response.startsWith("250 "));
    }

    public boolean stor(File file) throws IOException {
        if (file.isDirectory()) {
            throw new IOException("SimpleFTP cannot upload a directory.");
        }

        String filename = file.getName();

        return stor(new FileInputStream(file), filename);
    }

    /**
     * Sends a file to be stored on the FTP server. Returns true if the file
     * transfer was successful. The file is sent in passive mode to avoid NAT or
     * firewall problems at the client end.
     *
     * @param inputStream
     * @param filename
     * @return
     * @throws java.io.IOException
     */
    public boolean stor(InputStream inputStream, String filename) throws IOException {
        String response;
        try (BufferedInputStream input = new BufferedInputStream(inputStream)) {
            sendLine("PASV");
            response = readLine();
            if (!response.startsWith("227 ")) {
                throw new IOException("SimpleFTP could not request passive mode: " + response);
            }
            String ip = null;
            int port = -1;
            int opening = response.indexOf('(');
            int closing = response.indexOf(')', opening + 1);
            if (closing > 0) {
                String dataLink = response.substring(opening + 1, closing);
                StringTokenizer tokenizer = new StringTokenizer(dataLink, ",");
                try {
                    ip = tokenizer.nextToken() + "." + tokenizer.nextToken() + "." + tokenizer.nextToken() + "." + tokenizer.nextToken();
                    port = Integer.parseInt(tokenizer.nextToken()) * 256 + Integer.parseInt(tokenizer.nextToken());
                } catch (NumberFormatException e) {
                    throw new IOException("SimpleFTP received bad data link information: " + response);
                }
            }
            sendLine("STOR " + filename);
            Socket dataSocket = new Socket(ip, port);
            response = readLine();
            if (!response.startsWith("150 ")) {
                throw new IOException("SimpleFTP was not allowed to send the file: " + response);
            }
            try (BufferedOutputStream output = new BufferedOutputStream(dataSocket.getOutputStream())) {
                byte[] buffer = new byte[4096];
                int bytesRead = 0;
                while ((bytesRead = input.read(buffer)) != -1) {
                    output.write(buffer, 0, bytesRead);
                }
                output.flush();
            }
        }

        response = readLine();
        return response.startsWith("226 ");
    }

    public boolean stordown(File file, String destinationPath) throws IOException {
        System.out.println("Download a file");
        if (file.isDirectory()) {
            throw new IOException("SimpleFTP cannot download a directory.");
        }

        String name = file.getName();

        System.out.println("Size :" + file.length());
        System.out.println(name);

        return stordown(new FileInputStream(file), name, destinationPath);
    }

    public boolean stordown(InputStream inputStream, String filename, String destinationPath) throws IOException {
        String response;
        try (BufferedInputStream input = new BufferedInputStream(inputStream)) {
            sendLine("PASV");
            response = readLine();
            if (!response.startsWith("227 ")) {
                throw new IOException("SimpleFTP could not request passive mode: "
                        + response);
            }
            String ip = null;
            int port = -1;
            int opening = response.indexOf('(');
            int closing = response.indexOf(')', opening + 1);
            if (closing > 0) {
                String dataLink = response.substring(opening + 1, closing);
                StringTokenizer tokenizer = new StringTokenizer(dataLink, ",");
                try {
                    ip = tokenizer.nextToken() + "." + tokenizer.nextToken() + "."
                            + tokenizer.nextToken() + "." + tokenizer.nextToken();
                    port = Integer.parseInt(tokenizer.nextToken()) * 256
                            + Integer.parseInt(tokenizer.nextToken());
                } catch (NumberFormatException e) {
                    throw new IOException(
                            "SimpleFTP received bad data link information: "
                            + response);
                }
            }
            sendLine("RETR " + destinationPath + System.getProperty("file.separator")
                    + filename);
            Socket dataSocket = new Socket(ip, port);
            response = readLine();
            if (!response.startsWith("150 ")) {
                throw new IOException(
                        "SimpleFTP was not allowed to send the file: " + response);
            }
            try (BufferedOutputStream output = new BufferedOutputStream(
                    dataSocket.getOutputStream())) {
                byte[] buffer = new byte[4096];
                int bytesRead = 0;
                while ((bytesRead = input.read(buffer)) != -1) {
                    output.write(buffer, 0, bytesRead);
                }
                output.flush();
            }
        }

        response = readLine();
        return response.startsWith("227 ");
    }

    /**
     * Enter binary mode for sending binary files.
     *
     * @return
     * @throws java.io.IOException
     */
    public boolean bin() throws IOException {
        sendLine("TYPE I");
        String response = readLine();
        return (response.startsWith("201 "));
    }

    /**
     * Enter ASCII mode for sending text files. This is usually the default
     * mode. Make sure you use binary mode if you are sending images or other
     * binary data, as ASCII mode is likely to corrupt them.
     *
     * @return
     * @throws java.io.IOException
     */
    public boolean ascii() throws IOException {
        sendLine("TYPE A");
        String response = readLine();
        return (response.startsWith("202 "));
    }

    /**
     * Sends a raw command to the FTP server.
     */
    private void sendLine(String line) throws IOException {
        if (socket == null) {
            throw new IOException("SimpleFTP is not connected.");
        }
        try {
            writer.write(line + "\r\n");
            writer.flush();
            if (DEBUG) {
                System.out.println("> " + line);
            }
        } catch (IOException e) {
            socket = null;
            throw e;
        }
    }

    private String readLine() throws IOException {
        String line = reader.readLine();
        if (DEBUG) {
            System.out.println("< " + line);
        }
        return line;
    }

    public File getFileTree() {
        return this.fileTree;
    }

    public JSONObject getJSONObject() {
        return this.oJson;
    }

    private Socket socket = null;
    private BufferedReader reader = null;
    private BufferedWriter writer = null;
    // TO BE COMPLETED - 1 LINE
    private ObjectInputStream is = null;
    private JSONObject oJson;
    private File fileTree;

    private static final boolean DEBUG = false;

}
